#include "adaptivepartitioner.h"
#include "job.h"

//AdaptivePartitioner* AdaptivePartitioner::instance = NULL;
//
//AdaptivePartitioner* AdaptivePartitioner::getInstance() {
//	if (instance==NULL) {
//		instance = new AdaptivePartitioner();
//	}
//	return instance;
//}


//Constructor
AdaptivePartitioner::AdaptivePartitioner(long double granularity_initial, long double granularity_final, long double percentage_startup,
						long double percentage_calisteady, long double variance, int speed_window) {
							
	//Exceptions
	if (granularity_initial <= 0) throw std::runtime_error("Application Initial Granularity can not be equal or less than 0");
    if (granularity_final <= 0) throw std::runtime_error("Application Final Granularity can not be equal or less than 0");
    if (percentage_startup <= 0 || percentage_startup >= 1) throw std::runtime_error("Application Startup Percentage can not be equal or less than 0 or equal or more than 1");
	if (percentage_calisteady <= 0 || percentage_calisteady >= 1) throw std::runtime_error("Application Calibration Steady-State Percentage can not be equal or less than 0 or equal or more than 1");
	if (percentage_startup + percentage_calisteady >= 1) throw std::runtime_error("Application Calibration Steady-Stata plus Startup Percentage can not be equal or more than 1");
	if (variance <= 0 || variance >= 1) throw std::runtime_error("Application Variance can not be equal or less than 0 or equal or more than 1");
    if (speed_window <= 0) throw std::runtime_error("Application Speed Window can not be equal or less than 0");
	
	this->granularity_initial = granularity_initial;
	this->granularity_final = granularity_final;
	this->percentage_startup = percentage_startup;
	this->percentage_calisteady = percentage_calisteady;
	this->variance = variance;
	this->speed_window = speed_window;
	
	this->granularity_new_heuristic = 0;
	this->new_heuristic_count = 0;
	
	this->workload_size = -1;
	this->workload_computed = 0;
	this->workload_scheduled = 0;
		
	this->speed_vector = new long double[ speed_window ];
	this->speed_vector_index = 0;
	
	this->granularity_current = this->granularity_previous = this->granularity_initial;
	this->speed_previous = this->speed_current = -1;
	this->granularity_bottom = this->granularity_top = -1;
	this->down = false;
	
	this->phase_current = STARTUP;				
							
}

//Destructor
AdaptivePartitioner::~AdaptivePartitioner() {
	delete speed_vector;
}

//Configure Workload
void AdaptivePartitioner::configure_workload(long double workload_size, Job *job, Grid *grid) {
	if (workload_size <= 0) throw std::runtime_error("Application Workload can not be equal or less than 0");
	this->workload_size = workload_size;
	this->job = job;
	this->grid = grid;
	for(int i = 0; i < speed_window; i++)
		speed_vector[ i ] = -1;
}

//Update Speed Vector
void AdaptivePartitioner::update_speed_vector(long double task_speed) {
	if( speed_vector_index < speed_window ) {
		speed_vector[ speed_vector_index++ ] = task_speed;
	} else {
		for(int i = 1; i < speed_window; i++ )
			speed_vector[ i-1 ] = speed_vector[ i ];
		speed_vector[ speed_window - 1 ] = task_speed;
	}
}

////Update Granularity
//void AdaptivePartitioner::update_granularity() {
//
//	if( speed_vector_index >= speed_window ) {
//		long double speed_sum = 0;
//		for(int i = 0; i < speed_window; i++) {
//			speed_sum += speed_vector[ i ];
//		}
//		speed_previous = speed_current;
//		speed_current = speed_sum / speed_window;
//	}
//	
//	if( phase_current == STARTUP )
//		fprintf(stdout,"STARTUP PHASE: granularity = %Lf   workload_computed = %Lf\n",granularity_current,workload_computed);
//	else if( phase_current == CALIBRATION )
//		fprintf(stdout,"CALIBRATION PHASE: granularity = %Lf   workload_computed = %Lf\n",granularity_current,workload_computed);
//	else if( phase_current == BINARY_SEARCH )
//		fprintf(stdout,"BINARY SEARCH PHASE: granularity = %Lf   workload_computed = %Lf\n",granularity_current,workload_computed);
//	else if( phase_current == CONCLUSION )
//		fprintf(stdout,"CONCLUSION PHASE: granularity = %Lf   workload_computed = %Lf\n",granularity_current,workload_computed);
//	
//	//If the process be in calibration phase
//	if( workload_computed >= workload_size * percentage_startup && workload_computed < workload_size * percentage_startup + workload_size * percentage_calisteady ) {
//		if(phase_current == STARTUP) phase_current = CALIBRATION;
//		//If application speed increase, process duplicates the granularity
//		if(!down && speed_previous < speed_current - variance * speed_current) {
//			granularity_bottom = granularity_current;
//			granularity_current *= 2;
//			granularity_top = granularity_current;
//		//If application speed increase in Binary Search Phase, process increase the granularity in binary search mode
//		} else if(down && speed_previous < speed_current - variance * speed_current) {
//			granularity_bottom = granularity_current;
//			granularity_current = ( granularity_top + granularity_bottom ) / 2;
//		//If application speed decrease, the binary search phase begins
//		} else if(speed_previous > speed_current + variance * speed_current) {
//			down = true;
//			granularity_top = granularity_current;
//			granularity_current = ( granularity_top + granularity_bottom ) / 2;
//			phase_current = BINARY_SEARCH;
//		}
//	//If the process be in conclusion phase
//	} else if( workload_computed > workload_size * percentage_startup + workload_size * percentage_calisteady ) {
//		granularity_current = granularity_final;
//		phase_current = CONCLUSION;
//	}
//	
//	if(workload_computed + granularity_current > workload_size) {
//		granularity_current = workload_size - workload_computed;
//	}
//
//	workload_computed += granularity_current;
//
//}

//Update Granularity
void AdaptivePartitioner::update_granularity(long double task_size) {
	
	fprintf (stdout, ">>>>>>>>>>>>>>. Update Granularity called with task size = %Lf .<<<<<<<<<<<<<<<<<<<<<<<<<< \n", task_size);
	

		if(phase_current == STARTUP) phase_current = STEADY_STATE;
		//If application speed increase, process duplicates the granularity
		if( (speed_previous < speed_current - variance * speed_current) ) {
			//if(granularity_current <= task_size) {
				granularity_bottom = granularity_current;
				granularity_current *= 2;
				granularity_top = granularity_current;
//			} else {
//				granularity_top = granularity_current;
//				granularity_current = task_size;
//				granularity_bottom = granularity_current / 2;
//			}
		//If application speed decrease, the binary search phase begins
		} else if( (speed_previous > speed_current + variance * speed_current) ) {
			//down = true;
			//if(granularity_current >= task_size) {
				granularity_top = granularity_current;
				granularity_current = ( granularity_top + granularity_bottom ) / 2;
				granularity_bottom = granularity_current / 2;
//			} else {
//				granularity_bottom = granularity_current;
//				granularity_current = task_size;
//				granularity_top = granularity_current;
//			}
		}


}

//Task Finished
void AdaptivePartitioner::task_finished(Task *task, int replica_index) {
	
	//Getting the task size and execution time
	long double task_size = task->getDataInput()->getSize();
	long double task_time = -1;

	workload_computed += task_size;

	//Getting the processor
	Processor *proc = NULL;
	if(replica_index >= 0) {
		task_time = task->getReplicaExecutionTime(replica_index);
		proc = task->get_processor_replica( replica_index );
	} else {
		task_time = task->getTaskExecutionTime();
		proc = task->get_processor_task();
	}

	//Verifying if the granularity of this proc is the same os current granularity,
	//after this, calculating the speed with this granularity
	if(	proc->current_granularity != task_size ) {
		proc->current_granularity = task_size;
		proc->mean_for_current_granularity = task_size / task_time;
	} else {
		proc->mean_for_current_granularity = (proc->mean_for_current_granularity + (task_size / task_time)) / 2;
	}

	//Incrementing the task_size counter
	if( task_size == granularity_new_heuristic ) {
		new_heuristic_count++;
	} else {
		granularity_new_heuristic = task_size;
		new_heuristic_count = 1;
	}

//fprintf(stdout, "Testing if all processors finished tasks with this current size\n");
	//Testing if all processors finished tasks with this current size
	int number_of_procs = 0;
	bool not_yet = false;
	int n_sites = this->grid->getNumberOfSites();
	for(int i = 0; i < n_sites; i++) {
		Site *s = this->grid->getSiteByIndex(i);
		int n_procs = s->getNumberOfProcessors();
		for(int j = 0; j < n_procs; j++) {
			number_of_procs++;
			Processor *p = s->getProcessorByIndex(j);
			
//fprintf(stdout, ">>> Processor %s tah com granularidade = %Lg\n", p->getName(), p->current_granularity);
			
			if( p->current_granularity != task_size ) {
				not_yet = true;
			}
		}
	}

	//Testing if the task_size counter is 4 * number_of_procs
	if( not_yet && new_heuristic_count >= 4 * number_of_procs ) {
		not_yet = false;
		for(int i = 0; i < n_sites; i++) {
			Site *s = grid->getSiteByIndex(i);
			int n_procs = s->getNumberOfProcessors();
			for(int j = 0; j < n_procs; j++) {
				Processor *p = s->getProcessorByIndex(j);
				if( p->current_granularity != task_size ) {
					p->current_granularity = task_size;
					p->mean_for_current_granularity = task_size / task_time;
				}
			}
		}
	}

	//Calculating the new speed of the grid
	if(!not_yet) {
		speed_previous = speed_current;
		long double ratio = 0;
		int n_sites = this->grid->getNumberOfSites();
		for(int i = 0; i < n_sites; i++) {
			Site *s = this->grid->getSiteByIndex(i);
			int n_procs = s->getNumberOfProcessors();
			for(int j = 0; j < n_procs; j++) {
				Processor *p = s->getProcessorByIndex(j);
				ratio += 1 / p->mean_for_current_granularity;
			}
		}
		speed_current = number_of_procs / ratio;
		this->reset_procs_speeds();
		
fprintf(stdout, ">>>>>>>>>>>>>>>>> speed_previous=%Lg speed_current=%Lg <<<<<<<<<<<<<<<\n" , speed_previous, speed_current);

		long double percentage = workload_size * percentage_startup + workload_size * percentage_calisteady;
		
fprintf(stdout, "[[[ workload_scheduled=%Lf workload_size=%Lf workload_percentage=%Lf ]]]\n" , workload_scheduled, workload_size, percentage );

		//If the process be in calibration phase
		if( workload_scheduled > workload_size * percentage_startup && workload_scheduled < percentage ) {
			update_granularity(task_size);
		//If the process be in conclusion phase
		} else if( workload_scheduled >= percentage ) {
			if(granularity_current > granularity_final)
				granularity_current = granularity_final;
			phase_current = CONCLUSION;
		}
	
	}

//	if(down) {
//		down = false;
//		for(int i = 0; i < number_of_procs; i++)
//			this->add_task(granularity_current / 2);
//		for(int i = 0; i < number_of_procs; i++)
//			this->add_task(granularity_current);
//		for(int i = 0; i < number_of_procs; i++)
//			this->add_task(granularity_current * 2);			
//	} else {	
		this->add_task(granularity_current);
//	}
	
//	if (! this->workload_completed() ) {
//		update_speed_vector( task_size / task_time );
//		update_granularity();		
//		this->add_task();
//	}

}

void AdaptivePartitioner::add_task(long double granularity) {
	
	if(granularity == -1) granularity = granularity_current;
	if(granularity < granularity_initial) granularity = granularity_initial;
	
	if (! this->workload_completed() ) {
		
		if( workload_scheduled >= (workload_size * percentage_startup) + (workload_size * percentage_calisteady) )
			if( granularity > granularity_final )
				granularity = granularity_final;
		
		if(workload_scheduled + granularity > workload_size)
			granularity = workload_size - workload_scheduled;
	
		workload_scheduled += granularity;
		job->addTask( granularity, granularity / 10 );	
		
		if( phase_current == STARTUP )
			fprintf(stdout,"\n\nSTARTUP PHASE: granularity = %Lf   workload_scheduled = %Lf\n\n",granularity,workload_scheduled);
		else if( phase_current == STEADY_STATE )
			fprintf(stdout,"\n\nSTEADY-STATE PHASE: granularity = %Lf   workload_scheduled = %Lf\n\n",granularity,workload_scheduled);
		else if( phase_current == CONCLUSION )
			fprintf(stdout,"\n\nCONCLUSION PHASE: granularity = %Lf   workload_scheduled = %Lf\n\n",granularity,workload_scheduled);
		
	}
}

bool AdaptivePartitioner::workload_completed() {
	return workload_scheduled >= workload_size;
}

//Start process
void AdaptivePartitioner::start_process() {
	if (workload_size <= 0) throw std::runtime_error("It is necessary to configure the workload.");
	
	fprintf(stdout,"START-UP PHASE: granularity = %Lf\n",granularity_initial);
	fprintf(stdout,"CONCLUSION PHASE: granularity = %Lf\n",granularity_final);	
	
	long double workload_startup = workload_size * percentage_startup;
	int number_of_tasks_startup = (int)(workload_startup / granularity_initial);
	for(int i = 0; i < number_of_tasks_startup; i++) {
		//update_granularity();
		//job->addTask( granularity_initial, granularity_initial / 10 );
		this->add_task(granularity_initial);
	}
}

void AdaptivePartitioner::reset_procs_speeds() {
	this->granularity_new_heuristic = -1;
	this->new_heuristic_count = 0;
	int n_sites = this->grid->getNumberOfSites();
	for(int i = 0; i < n_sites; i++) {
		Site *s = this->grid->getSiteByIndex(i);
		int n_procs = s->getNumberOfProcessors();
		for(int j = 0; j < n_procs; j++) {
			Processor *p = s->getProcessorByIndex(j);
			p->current_granularity = -1;
		}
	}
}
